package com.cskaoyan.javase.object._4hashcode;

/**
 * hashCode方法
 * 1.方法的声明：public native int hashCode();
 *      这是一个本地方法，这个方法没有final修饰，可以被重写。
 * 2.方法的作用：
 *      a.返回该对象的哈希码值。
 *      b.支持此方法是为了提高哈希表（例如 java.util.Hashtable 提供的哈希表）的性能。
 * hashCode方法表示一种哈希算法，是一种计算哈希值的规则：将一个对象转换成对应的int整数哈希值
 *
 * 3.Object当中的hashCode方法的默认实现：
 *      实际上，由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。（这一般是通过将该对象的内部地址转换成一个整数来实现的，但是 JavaTM 编程语言不需要这种实现技巧。）
 *      hashCode方法的默认实现可以看成是一个对象的十进制地址值
 *      这说明什么呢？默认情况下，哈希值是通过计算对象的地址值来获取的。这样的话，一般两个独立的对象的哈希值是不同的，而同一个对象（用两个引用调用方法）它们的哈希值必然是相同的。
 *
 *      所以这里就存在一个问题：
 *      这么判断两个引用指向的对象是同一个对象，或者说如果判断两个引用指向的对象是相同的，是相等的？
 *          需要使用equals方法
 *          也就是说，equals方法认为的对象相等
 *              就意味着这两个对象是映射中左边集合的同一个元素，它们的哈希值必须一样。
 *
 * 所以，如果同时不重写equals方法和hashCode方法，那么它们都是根据对象的地址（是不是同一个对象）判断的。
 *
 * 4.重写hashCode方法，必须连带equals方法一起重写，而且重写的规则应当是一致的：
 *      在很多时候，我们都需要重写hashCode方法，JDK文档中给我们标注了重写的规则和什么时候进行重写，如下：
 *      1. 在 Java 应用程序执行期间，在对同一对象多次调用 hashCode 方法时，必须一致地返回相同的整数，前提是将对象进行 equals比较时所用的信息没有被修改。
 *      这说明：hashCode方法重写后，使用的信息计算哈希值。这个信息和equals比较时所用的信息应该是一致的
 *      Student age name
 *      equals用age重写
 *      hashCode也应该用age重写
 *
 *      2. 如果根据 equals(Object)方法，两个对象是相等的，那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果。
 *      这是哈希映射的定义要求的，映射不允许出现一对多。对象相等，
 *          那么两个对象就是映射中的同一个元素，应该映射到同一个结果。
 *
 * 3. 如果根据 equals(java.lang.Object)方法，两个对象不相等，那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果。
 *      哈希冲突是肯定存在，允许出现哈希冲突。
 * 但是，程序员应该意识到，为不相等的对象生成不同整数结果可以提高哈希表的性能。
 *      但哈希冲突越少，哈希表性能越好，我们应该追求更好的哈希算法。
 *
 * 最后，还是要强调一下，hashCode方法和equals方法要重写必须一起重写，而且重写的依据必须是一样的，这样才能保证两个方法的结果具有一致性。比如一个Student类，如果equals方法依据其中的成员变量age和name重写的，那么hashCode方法也必须要依赖于这两个成员变量重写！！
 *      一致性指的是：
 *      equals方法返回true，那么哈希值必须相等。
 *      equals方法返回false，哈希值可以相等，也可以不相等。但不相等，减少哈希冲突会更好。
 *
 *
 * 一般情况下，hashCode方法的重写实现用快捷键alt+insert生成就可以了。但是一定要保证它和equals方法相同的条件。
 *
 * 注意事项：
 *      1. 一旦重写hashCode方法后，默认的toString方法就不会再打印地址值了。这是因为toString方法，默认调用的是本地方法的hashCode方法获取地址值，重写后不再是地址值了。
 *      所以建议在重写hashCode方法和equals方法后
 *      也顺手重写类中的toString方法
 *
 * 2. 如果类中有引用数据类型成员变量，那么就继续调用它的hashCode方法，也就需要重写它的hashCode方法。
 *
 * @since 14:27
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
       /* Student s1 = new Student(18, 100);
        Student s2 = new Student(18, 100);
        Student s3 = s1;
        System.out.println(s1.hashCode());
        System.out.println(s3.hashCode());
        System.out.println(s2.hashCode());
        System.out.println(s1.equals(s2));*/

        Student s1 = new Student(18, 100);
        Student s2 = new Student(18, 100);
        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

        System.out.println(s1);
        System.out.println(s2);

    }
}

class Student {
    int age;
    double score;

    public Student() {
    }

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }
}